Option Explicit On

Imports System.Math
Imports VB = Microsoft.VisualBasic

Public Module modTool
    'Public Declare Sub Sleep Lib "kernel32" (ByVal dwmilliseconds As Long)
    'Use System.Threading.Thread.Sleep()
    'Use Wait() instead

	Public Declare Function FlashWindow Lib "user32" (ByVal hwnd As Long, ByVal bInvert As Long) As Long
	Public Declare Sub keybd_event Lib "user32" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

#Region "Enums"
    '********************************************************
    'Used to define the area for the capture screen function*
    '********************************************************
    Public Enum Scope
        WholeScreen = 0
        ActiveWindow = 1
    End Enum
#End Region

#Region "Classes"
    ''' <summary>
    ''' Standard return class for all functions.
    ''' Provides Error, Exception, and ReturnData functionality
    ''' </summary>
    ''' <remarks></remarks>
    Public Class ClsError
        Dim cHasError As Boolean        'True if an error exists
        Dim cException As Exception     'Will always contain an exception.  If no exception is given, an empty one is created
        Dim cobjReturnData As Object    'Contains data of an unknown type.  User must code appropriately for data type.

        Public Sub New(ByVal pHasError As Boolean, ByRef pException As Exception, Optional ByVal pobjReturnData As Object = "")
            cHasError = pHasError
            cException = IIf(pException Is Nothing, New System.Exception(), pException)
            cobjReturnData = pobjReturnData
        End Sub

#Region "Properties"
        Public ReadOnly Property HasError As Boolean
            Get
                Return cHasError
            End Get
        End Property

        Public ReadOnly Property Exception As Exception
            Get
                Return cException
            End Get
        End Property

        Public ReadOnly Property ReturnData As Object
            Get
                Return cobjReturnData
            End Get
        End Property
#End Region
    End Class
#End Region

#Region "Structures"
    Public Structure LineType
        Dim Slope As Double
        Dim YIntercept As Double
    End Structure
#End Region

    '*************************************************************************************************************************
    'These overloaded functions increase the size of the given array by 1, or initialize an empty array with a single element*
    '*************************************************************************************************************************
#Region "Auto Redimension"
    'NOTE: Only use AutoRedim when absoutely necessary.  The overhead is high for handling arrays this was.  Lists are much better

    Public Function AutoRedim(ByRef Arr() As String) As Long
        If Arr Is Nothing Then
            AutoRedim = 0
        Else
            AutoRedim = UBound(Arr) + 1
        End If

        ReDim Preserve Arr(AutoRedim)
    End Function

    Public Function AutoRedim(ByRef Arr() As Boolean) As Long
        If Arr Is Nothing Then
            AutoRedim = 0
        Else
            AutoRedim = UBound(Arr) + 1
        End If

        ReDim Preserve Arr(AutoRedim)
    End Function

    Public Function AutoRedim(ByRef Arr() As Byte) As Long
        If Arr Is Nothing Then
            AutoRedim = 0
        Else
            AutoRedim = UBound(Arr) + 1
        End If

        ReDim Preserve Arr(AutoRedim)
    End Function

    Public Function AutoRedim(ByRef Arr() As Integer) As Long
        If Arr Is Nothing Then
            AutoRedim = 0
        Else
            AutoRedim = UBound(Arr) + 1
        End If

        ReDim Preserve Arr(AutoRedim)
    End Function
#End Region

    '**************************
    'Randomly generated values*
    '**************************
#Region "Random"
    '*******************************************
    'Randomly generates an Upper Case character*
    '*******************************************
    Public Function RndUpperChr() As String
        Dim sngRnd As Single

        sngRnd = Rnd() * 26
        sngRnd = Floor(sngRnd)
        If sngRnd = 26 Then sngRnd = 25
        RndUpperChr = Chr(sngRnd + &H41)
    End Function

    '*******************************************
    'Randomly generates an Lower Case character*
    '*******************************************
    Public Function RndLowerChr() As String
        Dim sngRnd As Single

        sngRnd = Rnd() * 26
        sngRnd = Floor(sngRnd)
        If sngRnd = 26 Then sngRnd = 25
        RndLowerChr = Chr(sngRnd + &H61)
    End Function
#End Region

    '**************************************************
    'Functions that interact with Windows forms/screen*
    '**************************************************
#Region "Window"
    '***********************************************
    'Captures the active window or the entire screen
    '***********************************************
    Public Function CaptureScreen(ByVal Scope1 As Scope) As Image
        Const VK_SNAPSHOT As Byte = 44
        Dim pic As Image

        Call keybd_event(VK_SNAPSHOT, Scope1, 0, 0)
        Application.DoEvents()
        pic = Clipboard.GetData(2)
        CaptureScreen = pic
    End Function
#End Region

#Region "Sort"
    '!!!!@@ is this depricated with list sorting?
    '************************************************
    'Sorts an array of str type based on ASCII value*
    '************************************************
    Public Sub SortAlphabetically(ByRef Arr() As String)
        Dim i As Long
        Dim j As Long
        Dim UpperBound As Long
        Dim TempArr() As String
        Dim UsedIndices() As Long
        Dim MinimumIndex As Long
        Dim ShortIndex As Long
        Dim LongIndex As Long

        UpperBound = UBound(Arr)

        ReDim TempArr(UpperBound)
        ReDim UsedIndices(UpperBound)

        For i = 0 To UpperBound
            For j = 0 To UpperBound 'Find the next unused string
                If UsedIndices(j) <> 1 Then
                    MinimumIndex = j
                    Exit For
                End If
            Next j

            For j = 0 To UpperBound 'Find the next string alphabetically
                If UsedIndices(j) <> 1 And j <> MinimumIndex Then
                    If Len(Arr(j)) < Len(Arr(MinimumIndex)) Then
                        ShortIndex = j
                        LongIndex = MinimumIndex
                    Else
                        ShortIndex = MinimumIndex
                        LongIndex = j
                    End If

                    For k As Integer = 1 To Len(Arr(ShortIndex))
                        If Asc(Mid(Arr(ShortIndex), k, 1)) < Asc(Mid(Arr(LongIndex), k, 1)) Then
                            MinimumIndex = ShortIndex
                            GoTo Found
                        ElseIf Asc(Mid(Arr(ShortIndex), k, 1)) > Asc(Mid(Arr(LongIndex), k, 1)) Then
                            MinimumIndex = LongIndex
                            GoTo Found
                        End If
                    Next k
                    MinimumIndex = ShortIndex
Found:
                End If
            Next j

            UsedIndices(MinimumIndex) = 1
            TempArr(i) = Arr(MinimumIndex)
        Next i

        Arr = TempArr
    End Sub

    '***************************************
    'Sorts an array of sng type numerically*
    '***************************************
    Public Sub SortNumericallyforIndex(ByRef Arr() As Single)
        Dim UpperBound As Long
        Dim TempArr() As Single
        Dim UsedIndices() As Long
        Dim Minimum As Single
        Dim MinimumIndex As Long

        UpperBound = UBound(Arr)

        ReDim TempArr(UpperBound)
        ReDim UsedIndices(UpperBound)

        For i As Long = 0 To UpperBound
            Minimum = 1 'Reset the minimum variable each time the array is checked
            For j As Long = 0 To UpperBound 'Find the next string alphabetically
                If UsedIndices(j) <> 1 Then
                    If Arr(j) < Minimum Then
                        Minimum = Arr(j)
                        MinimumIndex = j
                    End If
                End If
            Next j

            UsedIndices(MinimumIndex) = 1
            TempArr(i) = MinimumIndex 'Record the INDEX at which the next minimum value occurs
        Next i

        Arr = TempArr
    End Sub
#End Region

    '******************************************************
    'Removes all exact duplicate entries in the array Arr()
    '******************************************************
    Public Sub RemoveDuplicates(ByRef Arrlst As ArrayList)
        Dim i As Integer
        Dim j As Integer
        Dim TempArr As New ArrayList
        Dim Duplicate As Boolean

        TempArr.Clear()

        If Arrlst.Count <> 0 Then       'Ensure that the array is non-empty
            If Arrlst.Count > 1 Then     'If the array has 1 element, there cannot be dupicates
                For i = 1 To Arrlst.Count - 1    'Loop through the first term
                    Duplicate = False
                    For j = i + 1 To Arrlst.Count    'Loop through the second term
                        If Arrlst.Item(i - 1) = Arrlst.Item(j - 1) Then Duplicate = True 'Dupilcate is found
                    Next j

                    If Duplicate = False Then   'Record all non-duplicates
                        TempArr.Add(Arrlst.Item(i - 1))
                    End If
                Next i

                'Record the last element, becuase it failed to match any of the other elements
                TempArr.Add(Arrlst.Item(i - 1))
            Else    '1 element
                TempArr.Add(Arrlst.Item(0))
            End If
        End If

        Arrlst = TempArr
    End Sub

    Public Function ToFraction(ByVal dec As Double, ByVal Accuracy As Integer) As String
        Dim FracValue As Double
        Dim WholeNumber As Integer = Int(dec)

        dec = dec - WholeNumber
        If dec = 0 Then Return WholeNumber
        dec = Round(dec, Accuracy)

        '!!!! make this faster.  There's no reason to loop 1,000,000 times for this  Perhaps find common denominators

        For denominator As Integer = 1 To 1000
            For numerator As Integer = 1 To 1000
                FracValue = Round(numerator / denominator, Accuracy)
                If Abs(FracValue - dec) <= 10 ^ (-Accuracy) Then
                    If WholeNumber <> 0 Then
                        Return WholeNumber & " " & numerator & "/" & denominator
                    Else
                        Return numerator & "/" & denominator
                    End If
                End If
                If FracValue > dec Then Exit For
            Next numerator
        Next denominator

        Return "ERROR"
    End Function

    Public Function ToFraction2(ByVal dec As Double, ByVal Accuracy As Integer) As String
        Dim WholeNumber As Integer = 0
        Dim Denominator As Integer = 0
        Dim Reduced As Boolean = True

        WholeNumber = Int(dec)
        dec = dec - WholeNumber

        If dec = 0 Then
            ToFraction2 = WholeNumber
            Exit Function
        End If

        dec = Round(dec, Accuracy)
        Denominator = 1000
        dec = dec * Denominator  'Remove all decimals

        Do While Reduced = True
            Reduced = False
            For i As Integer = 2 To Int(Denominator / 2)
                If (dec Mod i = 0) And (Denominator Mod i = 0) Then
                    dec = dec / i
                    Denominator = Denominator / i
                    Reduced = True
                    Exit For
                End If
            Next i
        Loop

        If WholeNumber <> 0 Then
            Return CStr(WholeNumber) & " " & CStr(dec) & "/" & CStr(Denominator)
        Else
            Return CStr(dec) & "/" & CStr(Denominator)
        End If
    End Function

    'DEBUG
    Private Sub TestToFraction(dec As System.Object, e As System.EventArgs)
        Dim params() As String
        Dim initfraction As String
        Dim finalfraction As String
        Dim initvalue As Double
        Dim finalvalue As Double
        Dim val As Double

        For denominator As Integer = 1 To 100
            Debug.Print(denominator)
            For numerator As Integer = 1 To 1000
                initfraction = numerator & "/" & denominator
                val = numerator / denominator
                finalfraction = ToFraction(val, 7)
                params = Split(initfraction, "/")
                initvalue = params(0) / params(1)
                params = Split(finalfraction, "/")
                finalvalue = params(0) / params(1)
                If initvalue <> finalvalue Then
                    Debug.Print(initfraction & "," & finalfraction)
                    Stop
                End If
            Next numerator
        Next denominator

        'Dim params() As String
        'Dim wholenumber As Integer
        'Dim fractionreturn As String
        'Dim testnum As Double

        'For i As Integer = 1 To 1000
        '	Debug.Print(i)
        '	For j As Integer = 1 To 1000
        '		fractionreturn = ToFraction(i / j, 7)

        '		If InStr(fractionreturn, " ") > 0 Or InStr(fractionreturn, "/") = 0 Then
        '			wholenumber = Split(fractionreturn, " ")(0)
        '		Else
        '			wholenumber = 0
        '		End If

        '		If InStr(fractionreturn, "/") = 0 Then
        '			testnum = 0
        '		Else
        '			If InStr(fractionreturn, " ") Then
        '				params = Split(Split(fractionreturn, " ")(1), "/")
        '			Else
        '				params = Split(fractionreturn, "/")
        '			End If
        '			testnum = params(0) / params(1)
        '		End If

        '		If CStr(Round(i / j, 7)) <> CStr(wholenumber + Round(testnum, 7)) Then
        '			Debug.Print(Round(i / j, 7))
        '			Debug.Print(wholenumber + Round(testnum, 7))
        '			Debug.Print(i & "/" & j & "," & Round(i / j, 7))
        '			Debug.Print(ToFraction(i / j, 7) & "," & wholenumber + Round(testnum, 7))
        '			Stop
        '		End If
        '	Next j
        'Next i
    End Sub

#Region "Conversion"
    ''' <summary>
    ''' Converts a binary string value into the INT32 type.
    ''' </summary>
    ''' <param name="pstrBinary">The string of bits to convert.  Must be 32 bits in length or less.  Extra bit data will be truncated</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Bin2Int(ByVal pstrBinary As String) As Int32
        If Len(pstrBinary) <> 32 Then pstrBinary.PadLeft(32, "0")

        Bin2Int = 0 'Ensure the value is initialized to zero since the value is calculated through cumulative addition.

        For i As Byte = 2 To 32
            Bin2Int = Bin2Int + IIf(Mid(pstrBinary, i, 1) = "1", Pow(2, 32 - i), 0)
        Next i

        'Incorporate sign
        If Left(pstrBinary, 1) = "1" Then Bin2Int = Bin2Int - Pow(2, 31)
    End Function

    ''' <summary>
    ''' Converts a binary string value into the UINT32 type.
    ''' </summary>
    ''' <param name="pstrBinary">The string of bits to convert.  Must be 32 bits in length or less.  Extra bit data will be truncated</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Bin2UInt(ByVal pstrBinary As String) As UInt32
        If Len(pstrBinary) <> 32 Then pstrBinary.PadLeft(32, "0")

        Bin2UInt = 0

        For i As Byte = 1 To 32
            Bin2UInt = Bin2UInt + IIf(Mid(pstrBinary, i, 1) = "1", Pow(2, 32 - i), 0)
        Next i
    End Function

    ''' <summary>
    ''' Converts a binary string value into the INT16 type.
    ''' </summary>
    ''' <param name="pstrBinary">The string of bits to convert.  Must be 16 bits in length or less.  Extra bit data will be truncated</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Bin2Sht(ByVal pstrBinary As String) As Int16
        If Len(pstrBinary) <> 16 Then pstrBinary.PadLeft(16, "0")

        Bin2Sht = 0

        For i As Byte = 2 To 16
            Bin2Sht = Bin2Sht + IIf(Mid(pstrBinary, i, 1) = "1", Pow(2, 16 - i), 0)
        Next i

        'Incorporate sign
        If Left(pstrBinary, 1) = "1" Then Bin2Sht = Bin2Sht - Pow(2, 15)
    End Function

    ''' <summary>
    ''' Converts a binary string value into the UINT16 type.
    ''' </summary>
    ''' <param name="pstrBinary">The string of bits to convert.  Must be 16 bits in length or less.  Extra bit data will be truncated</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Bin2USht(ByVal pstrBinary As String) As UInt16
        If Len(pstrBinary) <> 16 Then pstrBinary.PadLeft(16, "0")

        Bin2USht = 0

        For i As Byte = 1 To 16
            Bin2USht = Bin2USht + IIf(Mid(pstrBinary, i, 1) = "1", Pow(2, 16 - i), 0)
        Next i
    End Function

    ''' <summary>
    ''' Converts a binary string value into the BYTE type.
    ''' </summary>
    ''' <param name="pstrBinary">The string of bits to convert.  Must be 8 bits in length or less.  Extra bit data will be truncated</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Bin2Byt(ByVal pstrBinary As String) As Byte
        If Len(pstrBinary) <> 8 Then pstrBinary.PadLeft(8, "0")

        Bin2Byt = 0

        For i As Byte = 1 To 8
            Bin2Byt = Bin2Byt + IIf(Mid(pstrBinary, i, 1) = "1", Pow(2, 8 - i), 0)
        Next i
    End Function

    ''' <summary>
    ''' Converts a BYTE into a binary string.
    ''' </summary>
    ''' <param name="pbytInput">The input value to convert</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Byt2Bin(ByVal pbytInput As Byte) As String
        Dim bytPower2 As Byte

        Byt2Bin = ""

        For n As Integer = 7 To 0 Step -1
            bytPower2 = Pow(2, n)
            If pbytInput And bytPower2 Then
                Byt2Bin = Byt2Bin & "1"
            Else
                Byt2Bin = Byt2Bin & "0"
            End If
        Next n
    End Function

    ''' <summary>
    ''' Converts a INT16 into a binary string.
    ''' </summary>
    ''' <param name="pshtInput">The input value to convert</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Sht2Bin(ByVal pshtInput As Int16) As String
        Dim shtPower2 As Int16

        Sht2Bin = ""
        For n As Integer = 14 To 0 Step -1
            shtPower2 = Pow(2, n)
            If pshtInput And shtPower2 Then
                Sht2Bin = Sht2Bin & "1"
            Else
                Sht2Bin = Sht2Bin & "0"
            End If
        Next n

        If pshtInput < 0 Then
            Sht2Bin = "1" & Sht2Bin
        Else
            Sht2Bin = "0" & Sht2Bin
        End If
    End Function

    ''' <summary>
    ''' Converts a INT32 into a binary string.
    ''' </summary>
    ''' <param name="pintInput">The input value to convert</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Int2Bin(ByVal pintInput As Int32) As String
        Dim intPower2 As Int32

        Int2Bin = ""

        For n As Integer = 30 To 0 Step -1
            intPower2 = Pow(2, n)
            If pintInput And intPower2 Then
                Int2Bin = Int2Bin & "1"
            Else
                Int2Bin = Int2Bin & "0"
            End If
        Next n

        'Add the sign bit
        If pintInput < 0 Then
            Int2Bin = "1" & Int2Bin
        Else
            Int2Bin = "0" & Int2Bin
        End If
    End Function

    ''' <summary>
    ''' Returns the lower BYTE of a INT16 value
    ''' </summary>
    ''' <param name="pushtValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function LByte(ByVal pushtValue As UInt16) As Byte
        Dim ushtTemp As UInt16

        If pushtValue < 0 Then
            ushtTemp = pushtValue + Pow(2, 32)
        Else
            ushtTemp = pushtValue
        End If

        LByte = ushtTemp Mod 256
    End Function

    ''' <summary>
    ''' Returns the upper BYTE of a INT16 value
    ''' </summary>
    ''' <param name="pushtValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function HByte(ByVal pushtValue As UInt16) As Byte
        Dim ushtTemp As UInt16

        If pushtValue < 0 Then
            ushtTemp = pushtValue + Pow(2, 32)
        Else
            ushtTemp = pushtValue
        End If

        HByte = (ushtTemp - (ushtTemp Mod Pow(2, 8))) / Pow(2, 8)
    End Function

    ''' <summary>
    ''' Returns the lower INT16 of a INT32 value
    ''' </summary>
    ''' <param name="pintValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function LSht(ByVal pintValue As Int32) As Int16
        Dim temp As UInt32
        Dim temp2 As UInt16

        If pintValue < 0 Then
            temp = pintValue + Pow(2, 32)
        Else
            temp = pintValue
        End If

        temp2 = temp Mod Pow(2, 16)

        If temp2 > Pow(2, 15) Then
            LSht = temp2 - Pow(2, 16)
        Else
            LSht = temp
        End If
    End Function

    ''' <summary>
    ''' Returns the upper INT16 of a INT32 value
    ''' </summary>
    ''' <param name="pintValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function HSht(ByVal pintValue As Int32) As Int16
        Dim temp As UInt32
        Dim temp2 As UInt16

        If pintValue < 0 Then
            temp = pintValue + Pow(2, 32)
        Else
            temp = pintValue
        End If

        temp2 = (temp - (temp Mod Pow(2, 16))) / Pow(2, 16)

        If temp2 >= Pow(2, 15) Then
            HSht = temp2 - Pow(2, 16)
        Else
            HSht = temp2
        End If
    End Function

    ''' <summary>
    ''' Concatenates two INT16 values to form a single INT32 value
    ''' </summary>
    ''' <param name="psht1">INT16 in first 16 bits, sS</param>
    ''' <param name="psht2">INT16 in second 16 bits, Ss</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Sht2Int(ByVal psht1 As Int16, ByVal psht2 As Int16) As Int32
        Dim ushtTemp As UInt16
        Dim uintTemp As UInt32

        If psht2 < 0 Then
            ushtTemp = psht2 + Pow(2, 16)
        Else
            ushtTemp = psht2
        End If

        uintTemp = CInt(psht1) + (Pow(2, 16) * CInt(ushtTemp))

        If uintTemp >= Pow(2, 31) Then
            Sht2Int = uintTemp - Pow(2, 32)
        Else
            Sht2Int = uintTemp
        End If
    End Function

    ''' <summary>
    ''' Concatenates two UINT16 values to form a single INT32 value
    ''' </summary>
    ''' <param name="pusht1">UINT16 in first 16 bits, sS</param>
    ''' <param name="pusht2">UINT16 in second 16 bits, Ss</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function USht2Int(ByVal pusht1 As UInt16, ByVal pusht2 As UInt16) As Int32
        If pusht2 > &H7FFF Then
            USht2Int = CInt(pusht1) + (Pow(2, 16) * (CInt(pusht2) - Pow(2, 16)))
        Else
            USht2Int = CInt(pusht1) + (Pow(2, 16) * CInt(pusht2))
        End If
    End Function

    ''' <summary>
    ''' Concatenates two BYTE values to form a single INT16 value
    ''' </summary>
    ''' <param name="pbyt1">Byte in first 8 bits, bB</param>
    ''' <param name="pbyt2">Byte in second 8 bits, Bb</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Byt2Sht(ByVal pbyt1 As Byte, ByVal pbyt2 As Byte) As Int16
        Dim ushtTemp As UInt16

        ushtTemp = CShort(pbyt1) + (256 * CShort(pbyt2))

        If ushtTemp >= Pow(2, 15) Then
            Byt2Sht = ushtTemp - Pow(2, 16)
        Else
            Byt2Sht = ushtTemp
        End If
    End Function

    ''' <summary>
    ''' Concatenates four BYTE values to form a single INT32 value
    ''' </summary>
    ''' <param name="pbyt1">Byte in first 8 bits, bbbB</param>
    ''' <param name="pbyt2">Byte in second 8 bits, bbBb</param>
    ''' <param name="pbyt3">Byte in third 8 bits, bBbb</param>
    ''' <param name="pbyt4">Byte in fourth 8 bits, Bbbb</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Byt2Int(ByVal pbyt1 As Byte, ByVal pbyt2 As Byte, ByVal pbyt3 As Byte, ByVal pbyt4 As Byte) As Int32
        Dim uintTemp As UInt32

        uintTemp = CInt(pbyt1) + (Pow(2, 8) * CInt(pbyt2)) + (Pow(2, 16) * CInt(pbyt3)) + (Pow(2, 24) * CInt(pbyt4))

        If uintTemp >= Pow(2, 31) Then
            Byt2Int = uintTemp - Pow(2, 32)
        Else
            Byt2Int = uintTemp
        End If
    End Function

    ''' <summary>
    ''' Returns the lower UINT16 of a INT32 value
    ''' </summary>
    ''' <param name="pintValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function LUShtInt(ByVal pintValue As Int32) As UInt16
        LUShtInt = pintValue And &HFFFF
    End Function

    ''' <summary>
    ''' Returns the upper UINT16 of a INT32 value
    ''' </summary>
    ''' <param name="pintValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function HUShtInt(ByVal pintValue As Int32) As UInt16
        Dim uintTemp As UInt32
        If pintValue < 0 Then
            uintTemp = pintValue + 2 ^ 32
        Else
            uintTemp = pintValue
        End If
        HUShtInt = (uintTemp And &HFFFF0000) / &H10000
    End Function

    ''' <summary>
    ''' Returns the lower UINT16 of a INT32 value
    ''' </summary>
    ''' <param name="puintValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function LUShtUInt(ByVal puintValue As UInt32) As UInt16
        LUShtUInt = puintValue And &HFFFF
    End Function

    ''' <summary>
    ''' Returns the upper UINT16 of a INT32 value
    ''' </summary>
    ''' <param name="puintValue">The input value</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function HUShtUInt(ByVal puintValue As UInt32) As UInt16
        HUShtUInt = (puintValue And &HFFFF0000) / &H10000
    End Function

    ''' <summary>
    ''' Concatenates two INT16 values to form a single UINT32 value
    ''' </summary>
    ''' <param name="psht1">INT16 in first 16 bits, sS</param>
    ''' <param name="psht2">INT16 in secondt 16 bits, Ss</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Sht2UInt(ByVal psht1 As Int16, ByVal psht2 As Int16) As UInt32
        Dim ushtTemp As UInt16

        If psht2 < 0 Then
            ushtTemp = psht2 + Pow(2, 16)
        Else
            ushtTemp = psht2
        End If

        Sht2UInt = CUInt(psht1) + (Pow(2, 16) * CUInt(ushtTemp))
    End Function

    ''' <summary>
    ''' Concatenates two UINT16 values to form a single UINT32 value
    ''' </summary>
    ''' <param name="psht1">INT16 in first 16 bits, sS</param>
    ''' <param name="psht2">INT16 in second 16 bits, Ss</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function USht2UInt(ByVal psht1 As UInt16, ByVal psht2 As UInt16) As UInt32
        USht2UInt = psht1 + (Pow(2, 16) * psht2)
    End Function

    ''' <summary>
    ''' Concatenates two BYTE values to form a single UINT16 value
    ''' </summary>
    ''' <param name="pbyt1">Byte in first 8 bits, bB</param>
    ''' <param name="pbyt2">Byte in second 8 bits, Bb</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Byt2USht(ByVal pbyt1 As Byte, ByVal pbyt2 As Byte) As UInt16
        Byt2USht = CUShort(pbyt1) + (256 * CUShort(pbyt2))
    End Function

    ''' <summary>
    ''' Concatenates four BYTE values to form a single UINT32 value
    ''' </summary>
    ''' <param name="pbyt1">Byte in first 8 bits, bbbB</param>
    ''' <param name="pbyt2">Byte in second 8 bits, bbBb</param>
    ''' <param name="pbyt3">Byte in third 8 bits, bBbb</param>
    ''' <param name="pbyt4">Byte in fourth 8 bits, Bbbb</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Byt2UInt(ByVal pbyt1 As Byte, ByVal pbyt2 As Byte, ByVal pbyt3 As Byte, ByVal pbyt4 As Byte) As UInt32
        Byt2UInt = CUInt(pbyt1) + (Pow(2, 8) * CUInt(pbyt2)) + (Pow(2, 16) * CUInt(pbyt3)) + (Pow(2, 24) * CUInt(pbyt4))
    End Function

    ''' <summary>
    ''' Converts a SINGLE value into a IEEE FLOAT value
    ''' </summary>
    ''' <param name="psngValue">The input value to convert</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Sng2Float(ByVal psngValue As Single) As UInt32
        Dim intExponent As Integer = 0
        Dim sngDecimalPortion As Single = 0
        Dim strFraction As String = ""
        Sng2Float = 0

        If psngValue = 0 Then Return 0
        If Single.IsInfinity(psngValue) Then
            Return 0
        End If

        If psngValue < 0 Then Sng2Float = Sng2Float + Pow(2, 31) 'Set the negative bit
        psngValue = Abs(psngValue)

        'Calculate exponent
        Do While psngValue >= 2
            psngValue = psngValue / 2
            intExponent = intExponent + 1
        Loop

        Do While psngValue < 1
            psngValue = psngValue * 2
            intExponent = intExponent - 1
        Loop

        Sng2Float = Sng2Float + CInt(intExponent + 127) * &H800000  'Set exponent bits

        'Calculate the binary fraction
        sngDecimalPortion = psngValue - Int(psngValue)

        For i As Byte = 0 To 22
            sngDecimalPortion = sngDecimalPortion * 2
            If sngDecimalPortion >= 1 Then
                sngDecimalPortion = sngDecimalPortion - 1
                strFraction = strFraction & "1"
            Else
                strFraction = strFraction & "0"
            End If
        Next i

        Sng2Float = Sng2Float + Bin2Int("000000000" & strFraction)
    End Function

    ''' <summary>
    ''' Converts a IEEE FLOAT value into a SINGLE value
    ''' </summary>
    ''' <param name="puintValue">The input value to convert</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Float2Sng(ByVal puintValue As UInt32) As Single
        Dim sngTemp As Single
        Dim strBinary As String = Int2Bin(puintValue)
        Dim achrBinaryBits() = strBinary.ToArray
        Float2Sng = 0

        'Array is backwards (MSB first)

        For i As Byte = 9 To 31
            sngTemp = sngTemp + IIf(achrBinaryBits(i) = "1", Pow(2, -(i - 8)), 0)
        Next i
        sngTemp = sngTemp + 1

        sngTemp = sngTemp * Pow(2, Bin2Byt(Mid(strBinary, 2, 8)) - 127)

        If achrBinaryBits(0) = "1" Then sngTemp = -sngTemp 'Examine the sign bit and make the value negative if necessary
        Float2Sng = sngTemp
    End Function
#End Region

#Region "Rounding"
    Public Function RoundUp(value As Double, decimals As Integer) As Double
        Return Math.Ceiling(value * (10 ^ decimals)) / (10 ^ decimals)
    End Function

    Public Function RoundDown(value As Double, decimals As Integer) As Double
        Return Math.Floor(value * (10 ^ decimals)) / (10 ^ decimals)
    End Function
#End Region

    '***
    'DEPRICATED: use PADLEFT and PADRIGHT functions
    '***
    '
    'Public Function PrependZeroes(ByVal pstrInput As String, ByVal pintLength As Integer) As String
    '    PrependZeroes = pstrInput

    '    Do While Len(PrependZeroes) < pintLength
    '        PrependZeroes = "0" & PrependZeroes
    '    Loop
    'End Function

    'Public Function AppendZeroes(ByVal pstrInput As String, ByVal pintLength As Byte) As String
    '    Do While Len(pstrInput) < pintLength
    '        pstrInput = pstrInput & "0"
    '    Loop

    '    Return pstrInput
    'End Function

    'Public Function Append(ByVal pstr As String, ByVal Length As Byte, ByVal chr As Char) As String
    '    Do While Len(pstr) < Length
    '        pstr = pstr & chr
    '    Loop

    '    Return pstr
    'End Function

    Public Function TimetoStr(ByVal pintHours As Integer, ByVal pintMinutes As Integer, pintSeconds As Integer) As String
        Dim strHours As String
        Dim strMinutes As String
        Dim strSeconds As String

        strHours = IIf(pintHours <> 0, pintHours & " hr ", "")
        strMinutes = IIf(pintMinutes <> 0, pintMinutes & " min ", "")
        strSeconds = IIf(pintSeconds <> 0, pintSeconds & " sec", "")
        TimetoStr = strHours & strMinutes & strSeconds

        If TimetoStr = "" Then
            TimetoStr = "0 s"
        ElseIf VB.Right(TimetoStr, 1) = " " Then
            TimetoStr = VB.Left(TimetoStr, Len(TimetoStr) - 1)
        End If
    End Function

    Public Function TimetoStr(ByVal pintSeconds As Integer) As String
        Dim intHours As Integer
        Dim intMinutes As Integer
        Dim intSeconds As Integer

        intHours = Int(pintSeconds / 3600)
        pintSeconds = pintSeconds - intHours * 3600

        intMinutes = Int(pintSeconds / 60)
        pintSeconds = pintSeconds - intMinutes * 60

        intSeconds = pintSeconds

        TimetoStr = TimetoStr(intHours, intMinutes, intSeconds)
    End Function

    Public Function ConvertToHex(ByVal pstrInput As String) As String
        If IsNumeric(pstrInput) Then
            ConvertToHex = "&H" & Hex(pstrInput)
        Else
            ConvertToHex = pstrInput
        End If
    End Function

#Region "Encryption"
    Public Function GetHash(ByVal pstrInputData As String) As String
        Dim md5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create
        Dim abytHash() As Byte = md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(pstrInputData))
        GetHash = New System.Numerics.BigInteger(abytHash).ToString 'Add reference to System.Numerics
    End Function

    Public Function Encrypt(ByVal pstrInputData As String, ByVal pstrPassword As String) As String
        Dim AES As New System.Security.Cryptography.RijndaelManaged
        Dim Hash_AES As New System.Security.Cryptography.MD5CryptoServiceProvider
        Dim strEncryptedData As String = ""

        Try
            Dim abytHash(31) As Byte
            Dim abytTemp As Byte() = Hash_AES.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(pstrPassword))

            Array.Copy(abytTemp, 0, abytHash, 0, 16)
            Array.Copy(abytTemp, 0, abytHash, 15, 16)
            AES.Key = abytHash
            AES.Mode = Security.Cryptography.CipherMode.ECB

            Dim DESEncrypter As System.Security.Cryptography.ICryptoTransform = AES.CreateEncryptor
            Dim abytBuffer As Byte() = System.Text.ASCIIEncoding.ASCII.GetBytes(pstrInputData)

            strEncryptedData = Convert.ToBase64String(DESEncrypter.TransformFinalBlock(abytBuffer, 0, abytBuffer.Length))
        Catch ex As Exception
        End Try

        Return strEncryptedData
    End Function

    Public Function Decrypt(ByVal pstrInputData As String, ByVal pstrPassword As String) As String
        Dim AES As New System.Security.Cryptography.RijndaelManaged
        Dim Hash_AES As New System.Security.Cryptography.MD5CryptoServiceProvider
        Dim strDecryptedData As String = ""

        Try
            Dim abytHash(31) As Byte
            Dim abytTemp As Byte() = Hash_AES.ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(pstrPassword))

            Array.Copy(abytTemp, 0, abytHash, 0, 16)
            Array.Copy(abytTemp, 0, abytHash, 15, 16)
            AES.Key = abytHash
            AES.Mode = Security.Cryptography.CipherMode.ECB

            Dim DESDecrypter As System.Security.Cryptography.ICryptoTransform = AES.CreateDecryptor
            Dim abytBuffer As Byte() = Convert.FromBase64String(pstrInputData)

            strDecryptedData = System.Text.ASCIIEncoding.ASCII.GetString(DESDecrypter.TransformFinalBlock(abytBuffer, 0, abytBuffer.Length))
        Catch ex As Exception
        End Try

        Return strDecryptedData
    End Function
#End Region

#Region "Is"
    Public Function isReadableText(ByVal pstr As String) As Boolean
        Dim bytASCII As Byte

        If Len(pstr) <= 0 Then Return False

        For Each Chr As Char In pstr
            bytASCII = Asc(Chr)

            If bytASCII < &H20 Or bytASCII > &H7E Then
                Return False
            End If
        Next Chr

        Return True
    End Function

    Public Function isReadableTextOrNull(ByVal pstr As String) As Boolean
        Dim bytASCII As Byte

        If Len(pstr) <= 0 Then Return False

        For Each Chr As Char In pstr
            bytASCII = Asc(Chr)

            If (bytASCII < &H20 Or bytASCII > &H7E) And (Not bytASCII = &H0) Then
                Return False
            End If
        Next Chr

        Return True
    End Function

    Public Function IsByteHex(ByVal chr As Object) As Boolean
        IsByteHex = False

        If IsNumeric(chr) = True Then
            IsByteHex = True
        ElseIf chr = "a" Or chr = "A" Then
            IsByteHex = True
        ElseIf chr = "b" Or chr = "B" Then
            IsByteHex = True
        ElseIf chr = "c" Or chr = "C" Then
            IsByteHex = True
        ElseIf chr = "d" Or chr = "D" Then
            IsByteHex = True
        ElseIf chr = "e" Or chr = "E" Then
            IsByteHex = True
        ElseIf chr = "f" Or chr = "F" Then
            IsByteHex = True
        End If
    End Function

    Public Function IsHex(strInput As String) As Boolean
        Dim chr As Object

        Try
            If Len(strInput) <= 0 Then
                Return False
            Else
                For i As Integer = 1 To Len(strInput)
                    chr = VB.Mid(strInput, i, 1)

                    If IsByteHex(chr) = False Then
                        Return False
                    End If
                Next

                Return True
            End If
        Catch ex As Exception
            Return False
        End Try
    End Function

    Public Function IsPointIdentical(ByVal pt1 As PointF, ByVal pt2 As PointF, ByVal sngAccuracy As Single) As Boolean
        If Math.Abs(pt1.X - pt2.X) <= sngAccuracy And Math.Abs(pt1.Y - pt2.Y) <= sngAccuracy Then
            Return True
        Else
            Return False
        End If
    End Function
#End Region

#Region "Logs"
    Public Function WriteLogEntry(ByVal pstrFileLine As String, ByVal pstrLogFilePath As String) As ClsError
        Try
            Dim strDate As String
            Dim strLogFilePath As String
            Dim strmLogFile As System.IO.StreamWriter

            strDate = GetFormattedDate()
            strLogFilePath = pstrLogFilePath.Replace("#", strDate)
            'NOTE: Must add module 'modFileDIR.vb' because it contains the definition for GetContainingDirectory()
            If System.IO.Directory.Exists(GetContainingDirectory(strLogFilePath)) = False Then System.IO.Directory.CreateDirectory(GetContainingDirectory(strLogFilePath))
            strmLogFile = New System.IO.StreamWriter(strLogFilePath, True)
            strmLogFile.WriteLine(pstrFileLine)
            strmLogFile.Close()
            strmLogFile = Nothing
        Catch ex As Exception
            Return New ClsError(True, ex, "")
        End Try

        Return New ClsError(False, Nothing, "")
    End Function

    Public Function GetFormattedDate() As String
        Return Now.Day.ToString.PadLeft(2, "0") & "_" & MonthName(Now.Month).Substring(0, 3).ToUpper & "_" & Now.Year.ToString.PadLeft(4, "0")
    End Function

    Public Function GetFormattedTime() As String
        Return Now.Hour.ToString.PadLeft(2, "0") & "_" & Now.Minute.ToString.PadLeft(2, "0") & "_" & Now.Second.ToString.PadLeft(2, "0")
    End Function
#End Region

    Public Function MeasureTextWidth(ByVal pstrText As String, ByVal pfont As Font) As Long
        Return TextRenderer.MeasureText(pstrText, pfont).Width
    End Function

    Public Sub EnableButton(ByRef Button As System.Windows.Forms.Button)
        If Button.Enabled = False Then
            Button.Enabled = True
            Button.BackColor = Color.FromArgb(Button.BackColor.R Xor &HFF, Button.BackColor.G Xor &HFF, Button.BackColor.B Xor &HFF)
        End If
    End Sub

    Public Sub DisableButton(ByRef Button As System.Windows.Forms.Button)
        If Button.Enabled Then
            Button.Enabled = False
            '            Button.BackColor = Color.FromArgb((Button.BackColor.R + 128) Mod 256, (Button.BackColor.G + 128) Mod 256, (Button.BackColor.B + 128) Mod 256)
            Button.BackColor = Color.FromArgb(Button.BackColor.R Xor &HFF, Button.BackColor.G Xor &HFF, Button.BackColor.B Xor &HFF)
        End If
    End Sub

    Public Function GetClassPropertyCount(ByVal obj As Object) As Long
        Return obj.GetType.GetProperties().Count
    End Function


#Region "Lists"
    'DEPRICATED use .Sum()
#Region "Add List Items"
    Public Function AddListItems(ByVal lst As List(Of Integer)) As Integer
        Dim intSum As Integer = 0

        For Each lstItem As Integer In lst
            intSum = intSum + lstItem
        Next lstItem

        AddListItems = intSum
    End Function

    Public Function AddListItems(ByVal lst As List(Of Single)) As Single
        Dim sngSum As Single = 0

        For Each lstItem As Single In lst
            sngSum = sngSum + lstItem
        Next lstItem

        AddListItems = sngSum
    End Function

    Public Function AddListItems(ByVal lst As List(Of Long)) As Long
        Dim lngSum As Long = 0

        For Each lstItem As Long In lst
            lngSum = lngSum + lstItem
        Next lstItem

        AddListItems = lngSum
    End Function

    Public Function AddListItems(ByVal lst As List(Of Double)) As Double
        Dim dblSum As Double = 0

        For Each lstItem As Double In lst
            dblSum = dblSum + lstItem
        Next lstItem

        AddListItems = dblSum
    End Function

    Public Function AddListItems(ByVal lst As List(Of UInt16)) As UInt16
        Dim ushtSum As UInt16 = 0

        For Each lstItem As UInt16 In lst
            ushtSum = ushtSum + lstItem
        Next lstItem

        AddListItems = ushtSum
    End Function
#End Region
#End Region

    ''' <summary>
    ''' Attempts to mimic a timer wait or Sleep function by consuming processor time inside a for loop.
    ''' </summary>
    ''' <param name="plngMilliSeconds">Total milliseconds to wait</param>
    ''' <param name="plngOneMilliSecondForLoopCount">Precalculated count to acheive a 1 millisecond delay</param>
    ''' <remarks>Testing must be done to determine plngOneMilliSecondForLoopCount correctly</remarks>
    Public Sub Wait(ByVal plngMilliSeconds As Long, ByVal plngOneMilliSecondForLoopCount As Long)
        For i As Long = 1 To plngMilliSeconds * plngOneMilliSecondForLoopCount
        Next i
    End Sub

#Region "Linear Math"
    ''' <summary>
    ''' Calculates the parameters for a line (y = Mx + B) given 2 points.
    ''' </summary>
    ''' <param name="pt1">Point 1</param>
    ''' <param name="pt2">Point 2</param>
    ''' <returns>Returns Slope and Y-Intercept in the form of a LineType object</returns>
    ''' <remarks></remarks>
    Public Function FindLine(ByVal pt1 As PointF, ByVal pt2 As PointF) As LineType
        FindLine.Slope = (pt1.Y - pt2.Y) / (pt1.X - pt2.X)
        FindLine.YIntercept = pt1.Y - FindLine.Slope * pt1.X
    End Function

    ''' <summary>
    ''' Determines whether the cut should be curved, to account for the inlay (bit cut on the other side of the pattern).
    ''' </summary>
    ''' <param name="pptnm1">The N-1 point</param>
    ''' <param name="pptn">The N point</param>
    ''' <param name="pptnp1">The N+1 point</param>
    ''' <param name="pbolnnm1CutBelow">Determines whether we are cutting below the N-1 line</param>
    ''' <param name="pbolnnp1CutBelow">Determines whether we are cutting below the N+1 line</param>
    ''' <returns>Returns Slope and Y-Intercept in the form of a LineType object</returns>
    ''' <remarks>Occurs when the cut is for >180 degrees</remarks>
    Public Function isArcCut(ByVal pptnm1 As PointF, ByVal pptn As PointF, ByVal pptnp1 As PointF, ByVal pbolnnm1CutBelow As Boolean, ByVal pbolnnp1CutBelow As Boolean) As Boolean
        Dim bolA As Boolean
        Dim bolB As Boolean
        Dim bolC As Boolean
        Dim bolD As Boolean
        Dim bolE As Boolean
        Dim bolF As Boolean
        Dim bolG As Boolean
        Dim sngnm1Slope As Single
        Dim sngnp1Slope As Single

        sngnm1Slope = (pptnm1.Y - pptn.Y) / (pptnm1.X - pptn.X)
        sngnp1Slope = (pptn.Y - pptnp1.Y) / (pptn.X - pptnp1.X)

        bolA = (pptn.X > pptnm1.X)
        bolB = (pptnp1.X > pptn.X)
        bolC = Abs(sngnm1Slope) < Abs(sngnp1Slope)
        bolD = sngnm1Slope < 0
        bolE = sngnp1Slope < 0
        bolF = pbolnnm1CutBelow
        bolG = pbolnnp1CutBelow

        Return (bolA And bolB And bolC And ((Not bolE And Not bolF And Not bolG) Or (bolE And bolF And bolG))) Or _
            (bolA And bolB And Not bolC And ((Not bolD And bolF And bolG) Or (bolD And Not bolF And Not bolG))) Or _
            (bolA And Not bolB And bolC And ((Not bolE And bolF And Not bolG) Or (bolE And Not bolF And bolG))) Or _
            (bolA And Not bolB And Not bolC And ((Not bolD And Not bolF And bolG) Or (bolD And bolF And Not bolG))) Or _
            (Not bolA And bolB And bolC And ((Not bolE And Not bolF And bolG) Or (bolE And bolF And Not bolG))) Or _
            (Not bolA And bolB And Not bolC And ((Not bolD And bolF And Not bolG) Or (bolD And Not bolF And bolG))) Or _
            (Not bolA And Not bolB And bolC And ((Not bolE And bolF And bolG) Or (bolE And Not bolF And Not bolG))) Or _
            (Not bolA And Not bolB And Not bolC And ((Not bolD And Not bolF And Not bolG) Or (bolD And bolF And bolG)))

        'Return (bolA And bolB And bolC And Not bolE And Not bolF And Not bolG) Or _
        '            (bolA And bolB And bolC And bolE And bolF And bolG) Or _
        '            (bolA And bolB And Not bolC And Not bolD And bolF And bolG) Or _
        '            (bolA And bolB And Not bolC And bolD And Not bolF And Not bolG) Or _
        '            (bolA And Not bolB And bolC And Not bolE And bolF And Not bolG) Or _
        '            (bolA And Not bolB And bolC And bolE And Not bolF And bolG) Or _
        '            (bolA And Not bolB And Not bolC And Not bolD And Not bolF And bolG) Or _
        '            (bolA And Not bolB And Not bolC And bolD And bolF And Not bolG) Or _
        '            (Not bolA And bolB And bolC And Not bolE And Not bolF And bolG) Or _
        '            (Not bolA And bolB And bolC And bolE And bolF And Not bolG) Or _
        '            (Not bolA And bolB And Not bolC And Not bolD And bolF And Not bolG) Or _
        '            (Not bolA And bolB And Not bolC And bolD And Not bolF And bolG) Or _
        '            (Not bolA And Not bolB And bolC And Not bolE And bolF And bolG) Or _
        '            (Not bolA And Not bolB And bolC And bolE And Not bolF And Not bolG) Or _
        '            (Not bolA And Not bolB And Not bolC And Not bolD And Not bolF And Not bolG) Or _
        '            (Not bolA And Not bolB And Not bolC And bolD And bolF And bolG)
    End Function

    ''' <summary>
    ''' Calculates the point to cut to for an angle less than 180 on the inside of a given shape
    ''' </summary>
    ''' <param name="nm1">the n-1 point</param>
    ''' <param name="n"> the n point</param>
    ''' <param name="np1">the n+1 point</param>
    ''' <param name="pbolnnp1CutBelow">Determines if we are cutting below the n:n+1 line</param>
    ''' <param name="pbolnnm1CutBelow">Determines if we are cutting below the n:n-1 line</param>
    ''' <param name="psngBitDiameter">The diameter of the bit cutting</param>
    ''' <returns></returns>
    ''' <remarks>All math is constructed with the object upside down.  Cartesian coordinates increase Y values upward.  The CNC increases Y values downward.</remarks>
    Public Function CNC_InsidePoint(ByVal nm1 As PointF, ByVal n As PointF, ByVal np1 As PointF, ByVal pbolnnp1CutBelow As Boolean, ByVal pbolnnm1CutBelow As Boolean, ByVal psngBitDiameter As Single) As PointF
        Dim Linennp1 As LineType        'Line through points N and N+1
        Dim Linennm1 As LineType        'Line through points N-1 and N
        Dim Linennp1Inside As LineType  'Line parallel to Linennp1 but inide separated by radius of bit
        Dim Linennm1Inside As LineType  'Line parallel to Linennm1 but inide separated by radius of bit

        'Lines through points
        Linennp1 = FindLine(n, np1)
        Linennm1 = FindLine(n, nm1)

        '************
        'Inside lines
        '************
        'n:n+1
        Linennp1Inside.Slope = Linennp1.Slope
        If pbolnnp1CutBelow Then
            Linennp1Inside.YIntercept = Linennp1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Inside.Slope))
        Else
            Linennp1Inside.YIntercept = Linennp1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Inside.Slope))
        End If

        'n:n-1
        Linennm1Inside.Slope = Linennm1.Slope
        If pbolnnm1CutBelow Then
            Linennm1Inside.YIntercept = Linennm1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Inside.Slope))
        Else
            Linennm1Inside.YIntercept = Linennm1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Inside.Slope))
        End If

        'Intersection of inside lines
        CNC_InsidePoint.X = (Linennm1Inside.YIntercept - Linennp1Inside.YIntercept) / (Linennp1Inside.Slope - Linennm1Inside.Slope)
        CNC_InsidePoint.Y = CNC_InsidePoint.X * Linennp1Inside.Slope + Linennp1Inside.YIntercept
    End Function

    ''' <summary>
    ''' Calculates the points for an arc around a corner greater than 180 on the inside of a given shape
    ''' </summary>
    ''' <param name="nm1">the n-1 point</param>
    ''' <param name="n"> the n point</param>
    ''' <param name="np1">the n+1 point</param>
    ''' <param name="pbolnnp1CutBelow">Determines if we are cutting below the n:n+1 line</param>
    ''' <param name="pbolnnm1CutBelow">Determines if we are cutting below the n:n-1 line</param>
    ''' <param name="psngBitDiameter">The diameter of the bit cutting</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CNC_InsideArcPoints(ByVal nm1 As PointF, ByVal n As PointF, ByVal np1 As PointF, ByRef pbolnnp1CutBelow As Boolean, ByRef pbolnnm1CutBelow As Boolean, ByVal psngBitDiameter As Single) As List(Of PointF)
        Dim ptCircleCenter As PointF
        Dim Linennp1 As LineType        'Line through points N and N+1
        Dim Linennm1 As LineType        'Line through points N-1 and N
        Dim Linennp1Inside As LineType  'Line parallel to Linennp1 but inide separated by radius of bit
        Dim Linennm1Inside As LineType  'Line parallel to Linennm1 but inide separated by radius of bit
        Dim Linennp1OutsideforCircle As LineType  'Line parallel to Linennp1 but inide separated by radius of bit
        Dim Linennm1OutsideforCircle As LineType  'Line parallel to Linennm1 but inide separated by radius of bit
        Dim LineCirclePerpendicularLinennm1 As LineType
        Dim LineCirclePerpendicularLinennp1 As LineType
        Dim sngRadianArcStart As Single
        Dim sngRadianArcEnd As Single
        Dim intArcIntervals As Integer

        'Lines through points
        Linennp1 = FindLine(n, np1)
        Linennm1 = FindLine(n, nm1)

        '************
        'Inside lines
        '************
        'n:n+1
        Linennp1Inside.Slope = Linennp1.Slope
        If pbolnnp1CutBelow Then
            Linennp1Inside.YIntercept = Linennp1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Inside.Slope))
        Else
            Linennp1Inside.YIntercept = Linennp1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Inside.Slope))
        End If

        'n:n(-1)
        Linennm1Inside.Slope = Linennm1.Slope
        If pbolnnm1CutBelow Then
            Linennm1Inside.YIntercept = Linennm1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Inside.Slope))
        Else
            Linennm1Inside.YIntercept = Linennm1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Inside.Slope))
        End If

        '*************
        'Outside lines
        '*************
        'n:n+1
        Linennp1OutsideforCircle.Slope = Linennp1.Slope
        If pbolnnp1CutBelow Then
            Linennp1OutsideforCircle.YIntercept = Linennp1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Inside.Slope))
        Else
            Linennp1OutsideforCircle.YIntercept = Linennp1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Inside.Slope))
        End If

        'n:n-1
        Linennm1OutsideforCircle.Slope = Linennm1.Slope
        If pbolnnm1CutBelow Then
            Linennm1OutsideforCircle.YIntercept = Linennm1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Inside.Slope))
        Else
            Linennm1OutsideforCircle.YIntercept = Linennm1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Inside.Slope))
        End If

        'Find center of outside bit cutting to this point
        ptCircleCenter.X = (Linennm1OutsideforCircle.YIntercept - Linennp1OutsideforCircle.YIntercept) / (Linennp1OutsideforCircle.Slope - Linennm1OutsideforCircle.Slope)
        ptCircleCenter.Y = ptCircleCenter.X * Linennp1OutsideforCircle.Slope + Linennp1OutsideforCircle.YIntercept

        'Find lines perpendicular to the outside bit circle and the lines from nm1-n and n-np1
        LineCirclePerpendicularLinennp1.Slope = -1 / Linennp1.Slope
        LineCirclePerpendicularLinennp1.YIntercept = ptCircleCenter.Y - LineCirclePerpendicularLinennp1.Slope * ptCircleCenter.X

        LineCirclePerpendicularLinennm1.Slope = -1 / Linennm1.Slope
        LineCirclePerpendicularLinennm1.YIntercept = ptCircleCenter.Y - LineCirclePerpendicularLinennm1.Slope * ptCircleCenter.X

        'Find the angle, in radians, for the start and end of the inside arc
        sngRadianArcStart = Atan(LineCirclePerpendicularLinennm1.Slope)
        sngRadianArcEnd = Atan(LineCirclePerpendicularLinennp1.Slope)

        '*********************************************
        'Calcuate X intersection for arctan adjustment
        '*********************************************
        'n:n-1
        If (LineCirclePerpendicularLinennm1.YIntercept - Linennm1Inside.YIntercept) / (Linennm1Inside.Slope - LineCirclePerpendicularLinennm1.Slope) <= ptCircleCenter.X Then
            If sngRadianArcStart < 0 Then
                sngRadianArcStart = sngRadianArcStart + Math.PI
            Else
                sngRadianArcStart = sngRadianArcStart - Math.PI
            End If
        End If

        'n:n(+1)
        If (LineCirclePerpendicularLinennp1.YIntercept - Linennp1Inside.YIntercept) / (Linennp1Inside.Slope - LineCirclePerpendicularLinennp1.Slope) <= ptCircleCenter.X Then
            If sngRadianArcEnd < 0 Then
                sngRadianArcEnd = sngRadianArcEnd + Math.PI
            Else
                sngRadianArcEnd = sngRadianArcEnd - Math.PI
            End If
        End If

        'An arc cut will never be more than Pi long.  If it were, the outside cut would not be possible.
        'If it is greater than PI then we need to adjust the angles
        If Abs(sngRadianArcStart - sngRadianArcEnd) > Math.PI Then sngRadianArcEnd = sngRadianArcEnd - 2 * Math.PI

        If Abs(sngRadianArcStart - sngRadianArcEnd) * psngBitDiameter / 2 <= 0.1 Then   'Arc length is sufficiently small to forego an arc
            CNC_InsideArcPoints = New List(Of PointF)   'Initialize list of points
            CNC_InsideArcPoints.Add(New PointF(psngBitDiameter * Cos((sngRadianArcStart + sngRadianArcEnd) / 2) + ptCircleCenter.X, psngBitDiameter * Sin((sngRadianArcStart + sngRadianArcEnd) / 2) + ptCircleCenter.Y))
        Else    'Create an arc
            'Calculate the number of intervals to chop the arc up into for cutting.  Cut at least one point every 0.20mm of arc length to approximate the curve
            intArcIntervals = RoundUp(Math.Abs(sngRadianArcEnd - sngRadianArcStart) * (psngBitDiameter / 2) * 5, 0) '*5 for (1/0.20mm)

            CNC_InsideArcPoints = New List(Of PointF)   'Initialize list of points
            'Add the point for the beginning of the arc
            CNC_InsideArcPoints.Add(New PointF(psngBitDiameter * Cos(sngRadianArcStart) + ptCircleCenter.X, psngBitDiameter * Sin(sngRadianArcStart) + ptCircleCenter.Y))
            For i As Integer = 1 To intArcIntervals
                CNC_InsideArcPoints.Add(New PointF(psngBitDiameter * Cos(sngRadianArcStart + i * ((sngRadianArcEnd - sngRadianArcStart) / (intArcIntervals + 1))) + ptCircleCenter.X,
                                                   psngBitDiameter * Sin(sngRadianArcStart + i * ((sngRadianArcEnd - sngRadianArcStart) / (intArcIntervals + 1))) + ptCircleCenter.Y))
            Next i
            'Add the point for the end of the arc
            CNC_InsideArcPoints.Add(New PointF(psngBitDiameter * Cos(sngRadianArcEnd) + ptCircleCenter.X, psngBitDiameter * Sin(sngRadianArcEnd) + ptCircleCenter.Y))
        End If
    End Function

    ''' <summary>
    ''' Calculates the point to cut to for an angle less than 180 on the outside of a given shape
    ''' </summary>
    ''' <param name="nm1">the n-1 point</param>
    ''' <param name="n"> the n point</param>
    ''' <param name="np1">the n+1 point</param>
    ''' <param name="pbolnnp1CutBelow">Determines if we are cutting below the n:n+1 line</param>
    ''' <param name="pbolnnm1CutBelow">Determines if we are cutting below the n:n-1 line</param>
    ''' <param name="psngBitDiameter">The diameter of the bit cutting</param>
    ''' <returns></returns>
    ''' <remarks>All math is constructed with the object upside down.  Cartesian coordinates increase Y values upward.  The CNC increases Y values downward.</remarks>
    Public Function CNC_OutsidePoint(ByVal nm1 As PointF, ByVal n As PointF, ByVal np1 As PointF, ByVal pbolnnp1CutBelow As Boolean, ByVal pbolnnm1CutBelow As Boolean, ByVal psngBitDiameter As Single) As PointF
        Dim Linennp1 As LineType         'Line through points N and N+1
        Dim Linennm1 As LineType         'Line through points N-1 and N
        Dim Linennp1Outside As LineType  'Line parallel to Linennp1 but inide separated by radius of bit
        Dim Linennm1Outside As LineType  'Line parallel to Linennm1 but inide separated by radius of bit

        'Lines through points
        Linennp1 = FindLine(n, np1)
        Linennm1 = FindLine(n, nm1)

        '*************
        'Outside lines
        '*************
        'n:n+1
        Linennp1Outside.Slope = Linennp1.Slope
        If pbolnnp1CutBelow Then
            Linennp1Outside.YIntercept = Linennp1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Outside.Slope))
        Else
            Linennp1Outside.YIntercept = Linennp1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Outside.Slope))
        End If

        'n:n-1
        Linennm1Outside.Slope = Linennm1.Slope
        If pbolnnm1CutBelow Then
            Linennm1Outside.YIntercept = Linennm1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Outside.Slope))
        Else
            Linennm1Outside.YIntercept = Linennm1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Outside.Slope))
        End If

        'Intersection of Outside lines
        CNC_OutsidePoint.X = (Linennm1Outside.YIntercept - Linennp1Outside.YIntercept) / (Linennp1Outside.Slope - Linennm1Outside.Slope)
        CNC_OutsidePoint.Y = CNC_OutsidePoint.X * Linennp1Outside.Slope + Linennp1Outside.YIntercept
    End Function

    ''' <summary>
    ''' Calculates the points for an arc around a corner greater than 180 on the outside of a given shape
    ''' </summary>
    ''' <param name="nm1">the n-1 point</param>
    ''' <param name="n"> the n point</param>
    ''' <param name="np1">the n+1 point</param>
    ''' <param name="pbolnnp1CutBelow">Determines if we are cutting below the n:n+1 line</param>
    ''' <param name="pbolnnm1CutBelow">Determines if we are cutting below the n:n-1 line</param>
    ''' <param name="psngBitDiameter">The diameter of the bit cutting</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function CNC_OutsideArcPoints(ByVal nm1 As PointF, ByVal n As PointF, ByVal np1 As PointF, ByRef pbolnnp1CutBelow As Boolean, ByRef pbolnnm1CutBelow As Boolean, ByVal psngBitDiameter As Single) As List(Of PointF)
        Dim ptCircleCenter As PointF
        Dim Linennp1 As LineType        'Line through points N and N+1
        Dim Linennm1 As LineType        'Line through points N-1 and N
        Dim Linennp1Outside As LineType  'Line parallel to Linennp1 but inide separated by radius of bit
        Dim Linennm1Outside As LineType  'Line parallel to Linennm1 but inide separated by radius of bit
        Dim Linennp1InsideforCircle As LineType  'Line parallel to Linennp1 but inide separated by radius of bit
        Dim Linennm1InsideforCircle As LineType  'Line parallel to Linennm1 but inide separated by radius of bit
        Dim LineCirclePerpendicularLinennm1 As LineType
        Dim LineCirclePerpendicularLinennp1 As LineType
        Dim sngRadianArcStart As Single
        Dim sngRadianArcEnd As Single
        Dim intArcIntervals As Integer

        'Lines through points
        Linennp1 = FindLine(n, np1)
        Linennm1 = FindLine(n, nm1)

        'Outside lines
        Linennp1Outside.Slope = Linennp1.Slope
        If pbolnnp1CutBelow Then
            Linennp1Outside.YIntercept = Linennp1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Outside.Slope))
        Else
            Linennp1Outside.YIntercept = Linennp1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Outside.Slope))
        End If

        Linennm1Outside.Slope = Linennm1.Slope
        If pbolnnm1CutBelow Then
            Linennm1Outside.YIntercept = Linennm1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Outside.Slope))
        Else
            Linennm1Outside.YIntercept = Linennm1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Outside.Slope))
        End If

        'Inside lines
        Linennp1InsideforCircle.Slope = Linennp1.Slope
        If pbolnnp1CutBelow Then
            Linennp1InsideforCircle.YIntercept = Linennp1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Outside.Slope))
        Else
            Linennp1InsideforCircle.YIntercept = Linennp1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennp1Outside.Slope))
        End If

        Linennm1InsideforCircle.Slope = Linennm1.Slope
        If pbolnnm1CutBelow Then
            Linennm1InsideforCircle.YIntercept = Linennm1.YIntercept - (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Outside.Slope))
        Else
            Linennm1InsideforCircle.YIntercept = Linennm1.YIntercept + (psngBitDiameter / 2) / Sin((Math.PI / 2) - Atan(Linennm1Outside.Slope))
        End If

        'Find center of inside bit cutting to this point
        ptCircleCenter.X = (Linennm1InsideforCircle.YIntercept - Linennp1InsideforCircle.YIntercept) / (Linennp1InsideforCircle.Slope - Linennm1InsideforCircle.Slope)
        ptCircleCenter.Y = ptCircleCenter.X * Linennp1InsideforCircle.Slope + Linennp1InsideforCircle.YIntercept

        'Find lines perpendicular to the outside bit circle and the lines from nm1-n and n-np1
        LineCirclePerpendicularLinennp1.Slope = -1 / Linennp1.Slope
        LineCirclePerpendicularLinennp1.YIntercept = ptCircleCenter.Y - LineCirclePerpendicularLinennp1.Slope * ptCircleCenter.X

        LineCirclePerpendicularLinennm1.Slope = -1 / Linennm1.Slope
        LineCirclePerpendicularLinennm1.YIntercept = ptCircleCenter.Y - LineCirclePerpendicularLinennm1.Slope * ptCircleCenter.X

        'Find the angle, in radians, for the start and end of the inside arc
        sngRadianArcStart = Atan(LineCirclePerpendicularLinennm1.Slope)
        sngRadianArcEnd = Atan(LineCirclePerpendicularLinennp1.Slope)

        'Calcuate X intersection for arctan adjustment
        If (LineCirclePerpendicularLinennm1.YIntercept - Linennm1Outside.YIntercept) / (Linennm1Outside.Slope - LineCirclePerpendicularLinennm1.Slope) <= ptCircleCenter.X Then
            If sngRadianArcStart < 0 Then
                sngRadianArcStart = sngRadianArcStart + Math.PI
            Else
                sngRadianArcStart = sngRadianArcStart - Math.PI
            End If
        End If

        If (LineCirclePerpendicularLinennp1.YIntercept - Linennp1Outside.YIntercept) / (Linennp1Outside.Slope - LineCirclePerpendicularLinennp1.Slope) <= ptCircleCenter.X Then
            If sngRadianArcEnd < 0 Then
                sngRadianArcEnd = sngRadianArcEnd + Math.PI
            Else
                sngRadianArcEnd = sngRadianArcEnd - Math.PI
            End If
        End If

        If sngRadianArcEnd < sngRadianArcStart Then sngRadianArcEnd = sngRadianArcEnd + 2 * Math.PI

        intArcIntervals = RoundUp(Math.Abs(sngRadianArcEnd - sngRadianArcStart) * (psngBitDiameter / 2) * 5, 0) '*5 for (1/0.20mm)

        CNC_OutsideArcPoints = New List(Of PointF)
        CNC_OutsideArcPoints.Add(New PointF(psngBitDiameter * Cos(sngRadianArcStart) + ptCircleCenter.X, psngBitDiameter * Sin(sngRadianArcStart) + ptCircleCenter.Y))
        For i As Integer = 1 To intArcIntervals
            CNC_OutsideArcPoints.Add(New PointF(psngBitDiameter * Cos(sngRadianArcStart + i * ((sngRadianArcEnd - sngRadianArcStart) / (intArcIntervals + 1))) + ptCircleCenter.X,
                                               psngBitDiameter * Sin(sngRadianArcStart + i * ((sngRadianArcEnd - sngRadianArcStart) / (intArcIntervals + 1))) + ptCircleCenter.Y))
        Next i
        CNC_OutsideArcPoints.Add(New PointF(psngBitDiameter * Cos(sngRadianArcEnd) + ptCircleCenter.X, psngBitDiameter * Sin(sngRadianArcEnd) + ptCircleCenter.Y))
    End Function
#End Region

    Public Function FormatTime(ByVal ptimespan As TimeSpan) As String
        FormatTime = ""
        If ptimespan.Days > 0 Then FormatTime = ptimespan.Days & "day "
        If ptimespan.Hours > 0 Then FormatTime = FormatTime & ptimespan.Hours & "hr "
        If ptimespan.Minutes > 0 Then FormatTime = FormatTime & ptimespan.Minutes & "min "
        If ptimespan.Seconds > 0 Then FormatTime = FormatTime & ptimespan.Seconds & "sec"
        FormatTime = Trim(FormatTime)
    End Function
End Module
